InputManagerService负责Android输入设备的事件管理,输入事件是Android应用程序最重要的组成部分,因此我们有必要对整个事件的流程进行分析。我们知道输入事件如触摸事件的分发流程是从view树的根部开始向下传递的,但本篇不对此进行分析,而是从事件发生的源头到该步所做的工作进行梳理。那么既然IMS最终要将事件投递到view树中,即DecorView对象上,那么可以想到IMS必然和WMS有不可分割的关系,其实,也可以想到,输入事件要被应用进行处理,必然投递到某一个前台窗口,后面我们会看到他们之间的紧密关系。
事件输入的流程启动
我们知道SystemServer在启动的时候会启动众多的系统服务,这些服务中就包括了WMS和IMS。下面我们看看他们分别是如何启动的。
frameworks/base/services/java/com/android/server/SystemServer.java
1 | public void initAndLoop(){ |
SystemServer进程在启动时会调用其Main方法进行一些初始化工作,initAndLoop就是在整个时候进行的,它会注册多个系统服务到Sm中去,这里我们关注WMS和IMS即可。
首先创建IMS的实例,并将其作为参数传递给WMS,随后将这两个服务的实例都加入到SM中
这里我们先看IMS的实例创建过程,它需要一个context和一个Handler作为参数来构造。这个Handler时为WMS创建的,这里传给IMS,说明他们之间共享这个Handler.
SystemServer中通过该构造方法创建IMS 注意这个handler是供WMS使用的,它里传过来说明它想和WMS共享handler
frameworks/base/services/java/com/android/server/input/InputManagerService.java
1 | public InputManagerService(Context context, Handler handler) { |
IMS的构造很简单,它实际上调用natvieInit来创建native层的InputManger对象,对应于java层的IMS,结果返回给mPtr,注意这里的第三个参数为handler的MessageQueue.
frameworks/base/services/jni/com_android_server_input_InputManagerService.cpp
1 | //创建Native层的InputManager对象 |
首先我们得到java层传进来的MessageQueue对象,然后通过它构造一个NativeInputManager对象,并返回给上层。我们接着看其构造
frameworks/base/services/jni/com_android_server_input_InputManagerService.cpp
1 | //Native层的InputManger构造方法 |
这里我们主要创建两个对象,一个EventHub对象,它是用来监听输入事件,也就是输入设备产生的Input事件。另一个对象为InputManager,它以eventHub对象作为其参数构造,这个InputMangager才是真正c++层的IMS服务,NativeInputManager只能算是一个壳,它持有InputMangaer的引用罢了,真正的事情应该是在InputManager中进行的。
frameworks/base/services/input/InputManager.cpp
1 | //c++层真正的IMS服务类 |
InputManager的构造方法做了三件事:
- 创建InputDispatcher对象,这个对象使用来分派Input事件的
- 创建InputReader对象,它的主要任务从EventHub读取事件并交给InputDispatcher处理,所里在其构造中我们可以看到eventHub和mDispatcher
- 初始化InputManger,分别为InputReader和InputDispatcher创建线程,InputManager的主要工作就是在这两个线程中完成的。
到这里InputManager就创建完成了,但它并没有开始工作,因为线程还未跑起来,其实在我们之前SystemServer可以看到,IMS在创建好实例后还需要关联上WMS,然后设置一个Window回调后才调用start启动工作。这个start流程会调用nativeStart进行native层的InputManager来启动真正的工作。
1 | status_t InputManager::start() { |
在InputManager的start中我们会启动初始化中创建的两个线程,分别用来处理读取Input事件和分派Input事件。接下来我们就从这两个线程的工作入手分析Input事件从下到上的整个流程。
输入事件在IMS中的分派
我们先看Reader线程的工作,它就是不断的通过mReader的loopOnce读取事件。所以具体的工作还是InputReader进行的,我们接下来就看InputReader的实现。
frameworks/base/services/input/InputReader.cpp
1 | bool InputReaderThread::threadLoop() { |
frameworks/base/services/input/InputReader.cpp
1 | InputReader::InputReader(const sp<EventHubInterface>& eventHub, |
在InputReader的构造中我们传递了EventHub作为事件传递的源,还有InputDispatcher作为事件的分派者的listener,其中通过该listener构造了QueueInputListener对象,这个对象的用途后面我们再介绍。
frameworks/base/services/input/InputReader.cpp
1 | void InputReader::loopOnce() { |
InputReader的线程的主要工作就是调用loopOnce,在这个方法中会通过我们在构造方法中传递的mEventHub对象来获取Input事件,结果保存在mEventBuffer,并返回count代表我们读取的事件数目,接着通过processEventsLocked进行处理,实际上这里会将读取的事件保存在一个缓冲队列中,最后调用mQueueListener的flush将事件发送出去。下面我们接着看着个流程。
frameworks/base/services/input/InputReader.cpp
1 | void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) { |
我们看到事件消息一开始是作为一个RawEvent对象来根据其类型分别进行处理的,最基本的设备添加移除都通过专门的方法处理,其他事件都通过processEventsForDeviceLocked进行处理。至于addDeviceLocked,它是在添加输入设备时调用,用来添加输入设备的,在里面会对设备创建相应的InputDevice对象,同时创建会创建一些InputMapper,比如KeyboardInputMapper,TouchInputMapper等,这些InputMapper都保存在mMappers的列表中,后面我们会看到输入事件会交给这些mapper来进行处理。
1 | void InputReader::processEventsForDeviceLocked(int32_t deviceId, |
这一步我们通过mDevices取到对应的InputDevice,然后调用process进一步处理输入事件。
frameworks/base/services/input/InputReader.cpp
1 | void InputDevice::process(const RawEvent* rawEvents, size_t count) { |
到这一步,input事件就交给Device,这里我们看看KeyboardInputMapper对键盘输入事件的处理。
frameworks/base/services/input/InputReader.cpp
1 | void KeyboardInputMapper::process(const RawEvent* rawEvent) { |
在process进一步调用processKey,表示处理按键事件。
1 | void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode, |
这里首先对按键进行适当的处理,比如按键按下时,需要根据屏幕方向对按键的code进行旋转,这么做的原因可能是因为不同屏幕方向的key code表示的意义是不同的。还有如果对按下事件,如果已经存在了,就会发送持续的发送前一次的key code,否则就是按下事件,最后需要将key事件封装为一个NotifyKeyArgs参数,这些被封装的对象都是继承自NotifyArgs通过mQueuedListener,即QueuedInputListener的notifyKey分发这次Input消息。
frameworks/base/services/input/InputListener.cpp
1 | void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) { |
这个NotifyKey只是将封装的事件参数添加到一个参数队列mArgsQueue中,真正的发送是在flush中进行,它是在loopOnce中的最后调用的表示要将事件派发出去了。
frameworks/base/services/input/InputListener.cpp
1 | void QueuedInputListener::flush() { |
在flush中实际上调用的是NotifyArgs的notify方法。在其方法中调用了Listener的notifyKey这个listener即 QueuedInputListener的mInnerListener,这个mInnerListener即是在创建QueuedInputListener时传递的参数InputDispatcher,这个就是我们的派发者对象。因为InputReader的主要任务就是将读取的Input事件交给InputDispatcher进行处理。
frameworks/base/services/input/InputDispatcher.cpp
1 | void InputDispatcher::notifyKey(const NotifyKeyArgs* args) { |
InputDipatcher通过notifyKey来处理InputReader交给的事件,这里通过事件参数args来构造一个KeyEntry对象,并通过enqueuInboundEventLocked将其加入到一个队列中去,等待进行处理,并根据needWake来决定唤醒Looper。
frameworks/base/services/input/InputDispatcher.cpp
1 | bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) { |
enqueueInboundEventLocked将新的keyEvent加入到mInboundQueue中去,如果一开始这个队列是空的,就需要唤醒Looper了表示有新的事件需要处理。当事件到来后就会触发InputDipatcher的dispatchOnce,还记得?这个方法是在InputDipatcherThread的threadloop中循环调用进行事件处理的。我们看看它的具体实现
1 | void InputDispatcher::dispatchOnce() { |
这里事件的真正处理是在dispatchOnceInnerLocked中进行处理的。它会负责处理队列中的输入事件。然后通过pollOnce将事件发送出去,这是InputDispatcher的职责。下面我们看看dispatchOnceInnerLocked的具体实现。
frameworks/base/services/input/InputDispatcher.cpp
1 | void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) { |
这个方法首先判断mInboundQueue队列是否有事件要处理,有的话就先取出队头的事件放到mPendingEvent中,根据事件类型分别进行处理,这里我们看类型为TYPE_KEY的输入事件。然后调用dispatchKeyLocked进行处理。
1 | bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry, |
取出待处理的事件后通过dispatchKeyLocked进行处理,这个方法首先根据
findFocusedWindowTargetsLocked找到需要派发的目标对象inputTargets,然后通过
dispatchEventLocked进一步处理。
frameworks/base/services/input/InputDispatcher.cpp
1 | void InputDispatcher::dispatchEventLocked(nsecs_t currentTime, |
dispatchEventLocked方法主要是将待发送的输入事件发送给目标对象,这个目标对象是实际上为一个Connection,内部通过InputChannel构造,它是通过registerInputChannel中添加到一个mConnectionByFd的map中维护的。
1 | void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, |
prepareDispatchCycleLocked进一步调用enqueueDispatchEntriesLocked
1 | void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime, |
再接着调用startDispatchCycleLocked
1 | void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, |
到这里其实InputDispatcher的任务基本结束了,它已经将任务转给了具体的Connection进行处理,这个Connection即是再Dispatcher中注册的客户。下面我们分析这些Connection是如何注册到InputDispatcher的。这里我们从底层到上层追上去进行回顾整个流程
首先registerInputChannel是再NativeInputManager的registerInputChannel中调用的,而后者也应该是再Java层的InputManagerService中调用的,那么谁回去调用IMS的registerInputChannel呢?
InputChannel的注册
回顾我们之前再SystemServer中所做的事情,再IMS创建后会关联到WMS上,因为输入事件最终是要交给WMS负责分发给客户端的应用程序的。所以注册channel应该是再WMS上进行的。我们下来就看看这个注册的流程以及最终的事件是如何发布到上层的。首先我们去看那WMS的main方法
1 | public static WindowManagerService main(final Context context, |
在构造方法中实际上只是将IMS的实例保存在mInputManager中罢了,并没有任何注册的线索。那么注册的流程是在哪里呢?其实可以想象的到,当应用启动后,只有当界面显示出来的时候才能够接受输入事件,而且只有当前有焦点的窗口才可以对事件进行处理,而那些在任务栈中处于停止状态的窗口不对其进行处理。那么当窗口加入到WMS中时应该回去注册相应的channel用来接收事件。
1 | public void addView(View view, ViewGroup.LayoutParams params, |
这里我们主要看看ViewRootImpl的setView
1 | public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { |
在setView中会去创建InputChannel,在这之前会调用一次requestLayout,这里的主要工作就是设置当前窗口为WMS中具有焦点的窗口,为接受输入事件做准备。那么这个InputChannel会去注册到InputDispatcher中?实际上不是的,注意这个方法还在我们的应用端。而接受InputDispather也就是IMS的输入事件的是WMS,那么事件的流程应该是IMS到WMS然后再到应用端程序。下面我们追踪mInputChannel来看看具体是怎么样的。
我们先看到mInputChannel,它的创建过程,它的构造方法是个空方法,这么说InputChannel现在在java层只是一个空壳,而真正的初始化是在native层的。接着我们看到mInputChannel被传递给了addToDisplay,这个方法将当前窗口添加到WMS中,它会通过Binder调用WMS的addWindow方法。所以我们去WMS中看看addWindow的实现
frameworks/base/services/java/com/android/server/wm/WindowManagerService.java
1 | public int addWindow(Session session, IWindow client, int seq, |
这里的outInputChannel就是我们客户端传递的mInputChannel,这里我们看到首先我们调用InputChannel的openInputChannelPair方法打开了一对InputChannel,这一对InputChannel分别是为Client和Server端的channel,用来负责应用端以及WMS的输入事件的接受通道。其中索引为0的为服务端的,而索引为1的为客户端的。我们看到inputChannels[0]先被保存在win中,也即是WindowState中,WindowState表示当前窗口,随后通过mInputManager调用registerInputChannel注册到InputDispatcher中。
而inputchanel[1]被转换为outInputChannel作为出参传递给应用端。这里我们先分析注册到InputDispatcher的这个InputChannel,随后再分析传递到客户端的inputchannel。
首先我们分析openInputChannelPair流程。
frameworks/base/core/java/android/view/InputChannel.java
1 | public static InputChannel[] openInputChannelPair(String name) { |
这个方法直接调用了native层的nativeOpenInputChannelPair方法
frameworks/base/core/jni/android_view_InputChannel.cpp
1 | static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env, |
这个方法调用c++层的InputChannle(定义在InputTransport中)打开一对InputChannel对象,serverChannel和clientChannel,随后通过这两个对象调用android_view_input_channel_createInputChannel构造java层的InputChannel对象。
frameworks/native/libs/input/InputTransport.cpp
1 | status_t InputChannel::openInputChannelPair(const String8& name, |
首先通过socketpair创建一对相互连接的unnamed socket,这两个socket都可以互相进行读写,相比以前的管道通信,它是全双工的通信,即一端的socket既可以读又可以写。
创建好后,通过这两个socket分别去构造两个InputChannel作为服务端和客户端的读写通道。传递好后就可以将他们传递给java层使用了,这是通过android_view_input_channel_createInputChannel实现的。
1 | static jobject android_view_InputChannel_createInputChannel(JNIEnv* env, |
java层持有的其实是NativeInputChannel,它内部持有我们之前构造的InputChannel。
这个对象的地址会保存在java层的InputChannel的mPtr中。
创建好channel后接下来我们看看IMS是如何注册我们的服务端的InputChannel的
在之前addWindow中我们看到创建好Channel pair后就会调用IMS的registerInputChannel来注册服务端的Channel.
frameworks/base/services/java/com/android/server/input/InputManagerService.java
1 | public void registerInputChannel(InputChannel inputChannel, |
frameworks/base/services/jni/com_android_server_input_InputManagerService.cpp
1 | status_t NativeInputManager::registerInputChannel(JNIEnv* env, |
这个mINputManager就是我们c++层的InputMnager,通过getDispatcher获取到的InputDispatcher对象就是我们在创建InputManger对象时创建的mDispatcher。
frameworks/base/services/input/InputDispatcher.cpp
1 | status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel, |
这里我们通过传递进来的InputChannel先创建一个Connection对象,随后添加到一个mConnectionsByFd这个Map中去。需要注意的是在注册完成之后会去为这个描述符注册回调handleReceiveCallBack中。
1 | InputDispatcher::Connection::Connection(const sp<InputChannel>& inputChannel, |
这里可以看到通过InputChannel构造一个InputPublisher对象,这个InputChannel是我们服务端的Channel,它最终是给WMS使用的。还记得?在InputDispater中正是通过它的publishKeyEvent方法来发布输入事件到WMS的。
1 | status_t InputPublisher::publishKeyEvent( |
在publishKeyEvent中我们通过调用InputChannel的sendMessage将事件发送出去。
1 | status_t InputChannel::sendMessage(const InputMessage* msg) { |
在sendMessage中实际上我们使用的是为WMS注册InputChannel来发送的事件消息,它的接收端位于我们应用程序的另一个InputChannel。接下来我们就需要看看应用程序是如何接受这个输入事件的。
应用端输入事件的接受
在WMS的addView中我们构造了一对InputChannel,其中一个作为服务端注册到了InputDispatcher中,另一个作为结果返回给了应用端,应用端正是使用这个InputChannel来监听输入事件的。
在ViewRootImpl的setView中调用了addToDisplay后就得到了这个InputChannel,随后又使用该InputChannel构造了一个一个WindowInputEvetnReceiver对象
1 | if (mInputChannel != null) {//前面为该window创建了InputChannel所以不为null |
这个WindowInputEventReceiver的实现如下
1 | final class WindowInputEventReceiver extends InputEventReceiver { |
从其实现来看它继承自InputEventReceiver,是用来接受输入事件的,其中onInputEvent重载了接受事件的方法。
1 | public InputEventReceiver(InputChannel inputChannel, Looper looper) { |
这里的inputChannel是客户端使用的,它会通过nativeInit进行初始化,同时这个Looper是我们的主线程的Looper。
1 | static jint nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, |
nativeInit在native层创建对应的NativeInputEventReceiver对象,并调用了initialize进行初始化,同时将该native对象作为结果返回给java层的InputEventReceiver的mReceiverPtr成员。
1 | NativeInputEventReceiver::NativeInputEventReceiver(JNIEnv* env, |
NativeInputEventReceiver的构造方法很简单,会通过inputchannel构造一个InputConsumer mInputConsumer,从名字来看它是一个输入事件的消费者,也就是我们客户端程序。
1 | void NativeInputEventReceiver::setFdEvents(int events) { |
这里将InputChannel的描述符fd取出来,然后通过主线程的Looper进行监听,当需要读取输入事件时会触发handleEVent回调。
1 | int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) { |
随后调用consumeEvents进一步对事件进行消费
1 | status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, |
这里先调用InputConsumer的consume方法进行事件的读取,随后通过gInputReceiverClassInfo的dispatchInputEvent方法发布到java层的InputEventReceiver
frameworks/native/libs/input/InputTransport.cpp
1 | status_t InputConsumer::consume(InputEventFactoryInterface* factory, |
这里首先通过Client端的InputChannel来读取服务发送的输入事件,然后将结果保存在outEvent中
frameworks/native/libs/input/InputTransport.cpp
1 | status_t InputChannel::receiveMessage(InputMessage* msg) { |
接下来就到java层看看我们如何接受输入事件的,刚才说过,输入事件最终是通过InputEventReceiver的dispatchInputEvent接受的。
1 | // Called from native code. |
在dispatchInputEvent中是通过onInputEvent处理的,它真正的工作是它的子类WindowInputEventReceiver中进行处理的,它是在ViewRootImpl中定义的。
1 | final class WindowInputEventReceiver extends InputEventReceiver { |
这里先将待处理的事件入队,并调用doProcessInputEvents进一步处理。
1 | void doProcessInputEvents() { |
doProcessInputEvents的处理逻辑很简单,如果队列中有待处理的事件就循环调用deliverInputEvent进行处理。
1 | private void deliverInputEvent(QueuedInputEvent q) { |
在deliverInputEvent中,事件被交给一个InputStage进行处理,这个InputState实际上是一个职责链,事件沿着职责链进行传递,当某个Stage需要处理的话就调用apply来调用onProcess进行事件的消费。这个职责链是在ViewRootImpl的setview中创建的。
1 | // Set up the input pipeline. |
对于处理键盘输入事件,我们会交给viewPostImeStage进行处理
1 | final class ViewPostImeInputStage extends InputStage { |
在onProcess中如果事件类型为keyevent会交给processKeyEvent进行处理,后者会将事件投递给mView,即我们的DecorView,这样输入事件就可以view树上进行传递了。关于在view树中的处理流程,我们在另一篇中再做介绍。